package pers.chao.logger.core.aspect;

import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.core.Ordered;
import pers.chao.logger.annotation.AccessLog;
import pers.chao.logger.annotation.IgnoreLog;
import pers.chao.logger.constants.AccessType;
import pers.chao.logger.vo.AccessLogAnnDefine;
import pers.chao.logger.vo.AccessLogInfo;
import pers.chao.logger.vo.SortedListener;
import pers.chao.tool.aspect.AopUtil;
import pers.chao.tool.http.WebUtil;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * Description: 静态日志切面
 *
 * @author W.Chao
 * @date 2020/11/18 23:29
 **/
public class AccessLoggerSupport extends StaticMethodMatcherPointcutAdvisor {
	private final List<SortedListener> sortedListeners = new LinkedList<>();

	/**
	 * 提供接口添加监听者
	 *
	 * @param listener
	 * @return
	 */
	public void addListener(SortedListener listener) {
		sortedListeners.add(listener);
		// 符合默认的逆序规则
		// order相同时，默认将参数listener排到后面
		sortedListeners.sort((oldListener, newListener) -> oldListener.getOrder() != newListener.getOrder() ? oldListener.getOrder() - newListener.getOrder() : 1);
	}

	/**
	 * 静态切面
	 */
	public AccessLoggerSupport() {
		setAdvice((MethodInterceptor) methodInterceptor -> {
			MethodInterceptorHolder methodInterceptorHolder = MethodInterceptorHolder.create(methodInterceptor);

			final AccessLogInfo logInfo = buildLogInfo(methodInterceptorHolder);

			Object response = null;
			try {
				// 避免影响方法执行，可以在此处简单操作或异步操作
				sortedListeners.forEach(sortedListener -> sortedListener.getListener().before());
				response = methodInterceptor.proceed();

				// 完善后续日志信息
				logInfo.setResponse(response);
				logInfo.setResponseTimestamp(System.currentTimeMillis());
			} catch (Throwable e) {
				logInfo.setException(e);
				logInfo.setAction(AccessType.EXCEPTION.getValue());
				throw e;
			} finally {
				// 触发监听
				// 监听可在此处添加log的信息并继续传递
				// 可添加信息包含：操作人、操作人类型、附加消息map等
				sortedListeners.forEach(sortedListener -> sortedListener.getListener().after(logInfo));
			}
			return response;
		});
	}

	/**
	 * 构建AccessLog信息对象
	 *
	 * @param holder
	 * @return
	 */
	private AccessLogInfo buildLogInfo(MethodInterceptorHolder holder) {
		AccessLogAnnDefine annDefine = parseAnnotationContent(holder);
		HttpServletRequest request = WebUtil.getHttpServletRequest();

		// 构建访问前的日志信息
		return AccessLogInfo.builder()
				.setId(holder.getId())
				.setModule(annDefine.getModule())
				.setDescription(annDefine.getDescription())
				.setAction(annDefine.getAction())
				.setMethod(holder.getMethod())
				.setTarget(holder.getTarget().getClass())
				.setParameters(holder.getArgs())
				.setIp(WebUtil.getIpAddr(request))
				.setUrl(request.getRequestURL().toString())
				.setHttpHeaders(WebUtil.getHeaders(request))
				.setHttpMethod(request.getMethod())
				.setRequestTimestamp(System.currentTimeMillis())
				.build();
	}

	/**
	 * 解析AccessLog注解中的内容
	 *
	 * @param holder
	 * @return
	 */
	private AccessLogAnnDefine parseAnnotationContent(MethodInterceptorHolder holder) {
		AccessLog methodAnn = holder.findMethodAnnotation(AccessLog.class);
		AccessLog classAnn = holder.findClassAnnotation(AccessLog.class);
		String module = Stream.of(classAnn, methodAnn)
				.filter(Objects::nonNull)
				.map(AccessLog::module)
				.reduce((clazz, method) -> clazz.concat("-").concat(method))
				.orElse("");
		String description = Stream.of(classAnn, methodAnn)
				.filter(Objects::nonNull)
				.map(AccessLog::description)
				.reduce((clazz, method) -> clazz.concat("-").concat(method))
				.orElse("");
		String action = methodAnn != null ? methodAnn.type().getValue() : classAnn.type().getValue();
		return new AccessLogAnnDefine(module, description, action);
	}

	@Override
	public int getOrder() {
		return Ordered.HIGHEST_PRECEDENCE;
	}

	/**
	 * 判断是否匹配访问日志的条件
	 *
	 * @param method
	 * @param clazz
	 * @return
	 */
	@Override
	public boolean matches(Method method, Class<?> clazz) {
		AccessLog accessLog = AopUtil.findAnnotation(clazz, method, AccessLog.class);
		IgnoreLog ignoreLog = AopUtil.findAnnotation(clazz, method, IgnoreLog.class);
		return accessLog != null && ignoreLog == null;

//      等价如下:
//		if (accessLog == null || ignoreLog != null) {
//			return false;
//		}
//
//		return true;
	}
}
